package cn.source.util;

import cn.source.config.WxPayV3Config;
import cn.source.system.domain.PayResultResponse;
import com.wechat.pay.java.core.RSAPublicKeyConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.app.AppServiceExtension;
import com.wechat.pay.java.service.payments.app.model.Amount;
import com.wechat.pay.java.service.payments.app.model.PrepayRequest;
import com.wechat.pay.java.service.payments.h5.H5Service;
import com.wechat.pay.java.service.payments.h5.model.H5Info;
import com.wechat.pay.java.service.payments.h5.model.SceneInfo;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author coderwaves
 * @date 2023/11/27 11:41
 */
@Slf4j
public class WxV3PayUtil {
    private static WxPayV3Config wxPayV3Config;
    private static RSAPublicKeyConfig rsaAutoCertificateConfig;

    static {
        WxV3PayUtil.wxPayV3Config = SpringContextUtils.getBean(WxPayV3Config.class);
        WxV3PayUtil.rsaAutoCertificateConfig=SpringContextUtils.getBean(RSAPublicKeyConfig.class);
    }

    /**
     * 创建微信App支付订单 自动签名
     *
     * @param appId     appid
     * @param desc      订单名称描述信息
     * @param outTradNo 商户自定义订单号
     * @param notifyUrl 支付成功通知地址
     * @param moneyYuan 支付金额/元
     * @return 前端拉起支付参数
     */
    public static PayResultResponse.WxAppPayOrderInfo createWxAppPayOrder(String appId, String desc, String outTradNo, String notifyUrl, BigDecimal moneyYuan) {
        PrepayRequest request = new PrepayRequest();
        request.setAppid(appId);
        request.setMchid(wxPayV3Config.getMchId());
        request.setDescription(desc);
        request.setOutTradeNo(outTradNo);
        request.setNotifyUrl(notifyUrl);
        //request.setTimeExpire("2015-05-20T13:29:35+08:00");toRfc3339String(xxx)
        Amount amount = new Amount();
        //微信支付单位 分
        amount.setTotal(yuanToFen(moneyYuan));
        request.setAmount(amount);
        AppServiceExtension service = new AppServiceExtension.Builder()
                .config(rsaAutoCertificateConfig)
                .build();
        com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = service.prepayWithRequestPayment(request);

        PayResultResponse.WxAppPayOrderInfo orderInfo = new PayResultResponse.WxAppPayOrderInfo();
        orderInfo.setAppid(prepayWithRequestPaymentResponse.getAppid());
        orderInfo.setNoncestr(prepayWithRequestPaymentResponse.getNonceStr());
        orderInfo.setPackageStr(prepayWithRequestPaymentResponse.getPackageVal());
        orderInfo.setPartnerid(prepayWithRequestPaymentResponse.getPartnerId());
        orderInfo.setPrepayid(prepayWithRequestPaymentResponse.getPrepayId());
        orderInfo.setTimestamp(prepayWithRequestPaymentResponse.getTimestamp());
        orderInfo.setSign(prepayWithRequestPaymentResponse.getSign());
        return orderInfo;
    }

    /**
     * 创建微信JsApi支付订单 手动签名
     *
     * @param jsAppId   公众号appId
     * @param desc      订单名称描述信息
     * @param outTradNo 商户自定义订单号
     * @param notifyUrl 支付成功通知地址
     * @param moneyYuan 支付金额/元
     * @param openId    code换取的用户openId
     * @return 前端拉起支付参数
     */

    public static PayResultResponse.WxJsApiPayOrderInfo createWxJsPayOrder(String jsAppId, String desc, String outTradNo, String notifyUrl, BigDecimal moneyYuan, String openId) {
        com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest request = new com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest();
        request.setAppid(jsAppId);
        request.setMchid(wxPayV3Config.getMchId());
        request.setDescription(desc);
        request.setOutTradeNo(outTradNo);
        request.setNotifyUrl(notifyUrl);
        //request.setTimeExpire("2015-05-20T13:29:35+08:00");toRfc3339String(xxx)
        Payer payer = new Payer();
        payer.setOpenid(openId);
        request.setPayer(payer);
        com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount();
        //微信支付单位 分
        amount.setTotal(yuanToFen(moneyYuan));
        request.setAmount(amount);
        JsapiServiceExtension service = new JsapiServiceExtension.Builder()
                .config(rsaAutoCertificateConfig)
                .build();
        PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = service.prepayWithRequestPayment(request);
        PayResultResponse.WxJsApiPayOrderInfo orderInfo = new PayResultResponse.WxJsApiPayOrderInfo();
        orderInfo.setAppId(prepayWithRequestPaymentResponse.getAppId());
        orderInfo.setTimeStamp(prepayWithRequestPaymentResponse.getTimeStamp());
        orderInfo.setNonceStr(prepayWithRequestPaymentResponse.getNonceStr());
        orderInfo.setPackageStr(prepayWithRequestPaymentResponse.getPackageVal());
        orderInfo.setSignType(prepayWithRequestPaymentResponse.getSignType());
        orderInfo.setPaySign(prepayWithRequestPaymentResponse.getPaySign());
        return orderInfo;
    }

    /**
     * 创建微信JsApi支付订单 手动签名
     *
     * @param jsAppId   公众号appId
     * @param desc      订单名称描述信息
     * @param outTradNo 商户自定义订单号
     * @param notifyUrl 支付成功通知地址
     * @param moneyYuan 支付金额/元
     * @param openId    code换取的用户openId
     * @return 前端拉起支付参数
     */

    public static PayResultResponse.WxJsApiPayOrderInfo createWxJsPayOrder(String jsAppId, String desc, String outTradNo, String notifyUrl, BigDecimal moneyYuan) {
        com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest request = new com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest();
        request.setAppid(jsAppId);
        request.setMchid(wxPayV3Config.getMchId());
        request.setDescription(desc);
        request.setOutTradeNo(outTradNo);
        request.setNotifyUrl(notifyUrl);
        //request.setTimeExpire("2015-05-20T13:29:35+08:00");toRfc3339String(xxx)
        //Payer payer = new Payer();
        //payer.setOpenid(openId);
        //request.setPayer(payer);
        com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount();
        //微信支付单位 分
        amount.setTotal(yuanToFen(moneyYuan));
        request.setAmount(amount);
        JsapiServiceExtension service = new JsapiServiceExtension.Builder()
                .config(rsaAutoCertificateConfig)
                .build();
        PrepayWithRequestPaymentResponse prepayWithRequestPaymentResponse = service.prepayWithRequestPayment(request);
        PayResultResponse.WxJsApiPayOrderInfo orderInfo = new PayResultResponse.WxJsApiPayOrderInfo();
        orderInfo.setAppId(prepayWithRequestPaymentResponse.getAppId());
        orderInfo.setTimeStamp(prepayWithRequestPaymentResponse.getTimeStamp());
        orderInfo.setNonceStr(prepayWithRequestPaymentResponse.getNonceStr());
        orderInfo.setPackageStr(prepayWithRequestPaymentResponse.getPackageVal());
        orderInfo.setSignType(prepayWithRequestPaymentResponse.getSignType());
        orderInfo.setPaySign(prepayWithRequestPaymentResponse.getPaySign());
        return orderInfo;
    }
    /**
     * 创建微信PC H5支付订单 手动签名
     *
     * @param jsAppId   公众号appId
     * @param desc      订单名称描述信息
     * @param outTradNo 商户自定义订单号
     * @param notifyUrl 支付成功通知地址
     * @param moneyYuan 支付金额/元
     * @return 前端拉起支付参数
     */

    public static PayResultResponse.H5PayOrderInfo createWxH5PayOrder(String jsAppId, String desc, String outTradNo, String notifyUrl, BigDecimal moneyYuan, String payerClientIp) {
        com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
        request.setAppid(jsAppId);
        request.setMchid(wxPayV3Config.getMchId());
        request.setDescription(desc);
        request.setOutTradeNo(outTradNo);
        request.setNotifyUrl(notifyUrl);


        com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();
        //微信支付单位 分
        amount.setTotal(yuanToFen(moneyYuan));
        request.setAmount(amount);

        NativePayService service = new NativePayService.Builder()
                .config(rsaAutoCertificateConfig)
                .build();
        PrepayResponse prepayWithRequestPaymentResponse = service.prepay(request);
        PayResultResponse.H5PayOrderInfo orderInfo = new PayResultResponse.H5PayOrderInfo();

        orderInfo.setCodeUrl(prepayWithRequestPaymentResponse.getCodeUrl());
        return orderInfo;
    }

    /**
     * 微信申请退款请求
     *
     * @param outTradeNo      申请退款的原订单号 商户内部定义的订单号
     * @param totalMoneyYuan  退款订单的总金额
     * @param refundMoneyYuan 本次退款金额
     * @param outRefundNo     本次退款商户自定义退款单号
     * @param reason          本次退款原因
     * @param notifyUrl       退款通知地址
     * @return
     */
    public static Refund createWxRefundRequest(String outTradeNo, BigDecimal totalMoneyYuan, BigDecimal refundMoneyYuan, String outRefundNo, String reason, String notifyUrl) {
        RefundService createRefundBuild = new RefundService.Builder().config(rsaAutoCertificateConfig).build();
        CreateRequest refundRequest = new CreateRequest();
        refundRequest.setOutTradeNo(outTradeNo);
        AmountReq amount = new AmountReq();
        amount.setTotal((long) yuanToFen(totalMoneyYuan));
        amount.setRefund((long) yuanToFen(refundMoneyYuan));
        amount.setCurrency("CNY");
        refundRequest.setAmount(amount);
        refundRequest.setOutRefundNo(outRefundNo);
        refundRequest.setReason(reason);
        refundRequest.setNotifyUrl(notifyUrl);
        Refund createRefundResult = createRefundBuild.create(refundRequest);
        log.info("微信退款结果返回信息：{}", createRefundResult);
        return createRefundResult;
    }

    /**
     * 查询退款结果
     *
     * @param outRefundNo 退款单号
     * @return
     */
    public static Refund queryRefundRequestResult(String outRefundNo) {
        RefundService queryRefundBuild = new RefundService.Builder().config(rsaAutoCertificateConfig).build();
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.setOutRefundNo(outRefundNo);
        Refund queryRefundResult = queryRefundBuild.queryByOutRefundNo(request);
        log.info("查询订单退款结果：{}", queryRefundResult);
        return queryRefundResult;
    }

    /**
     * 元转换到分
     *
     * @param yuan
     * @return
     */
    public static int yuanToFen(BigDecimal yuan) {
        if (yuan == null) {
            return 0;
        }
        return yuan.setScale(2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).intValue();
    }

    /**
     * 分转换到元
     * @param fen
     * @return
     */
    public static BigDecimal fenToYuan(Long fen) {
        if (fen == null) {
            return BigDecimal.ZERO;
        }
        return BigDecimal.valueOf(fen).multiply(BigDecimal.valueOf(0.01)).setScale(2, RoundingMode.HALF_UP);
    }

    public static Date rfc3339ToDate(String rfc3339String) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.CHINA);
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        try {
            return sdf.parse(rfc3339String);
        } catch (ParseException e) {
            log.error("rfc3339转Date失败：{}", rfc3339String, e);
            return new Date();
        }
    }

    private static String toRfc3339String(Date date) {
        // 将Date转换为Instant
        Instant instant = date.toInstant();
        // 创建日期格式化器，并指定上海时区
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX")
                .withZone(ZoneId.of("Asia/Shanghai"));
        // 格式化Instant为字符串
        return formatter.format(instant);
    }

    /**
     * 支付通知
     *
     * @param success
     * @return
     */
    public static Map<String, String> notice(boolean success, HttpServletResponse response) {
        Map<String, String> result = new HashMap<>();
        result.put("code", success ? "SUCCESS" : "FAIL");
        if (success) {
            response.setStatus(HttpStatus.OK.value());
        } else {
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        }
        return result;
    }

    @SneakyThrows
    private static String getBodyString(HttpServletRequest request) {
        String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
        log.info("微信支付回调，请求体信息：{}", body);
        return body;
    }

    /**
     * 获取微信支付请求参数
     *
     * @param httpServletRequest
     * @return
     */
    private static RequestParam getRequestParam(HttpServletRequest httpServletRequest) {
        log.info("微信支付回调请求，参数数据信息：{}", httpServletRequest.getParameterMap());
        String signature = httpServletRequest.getHeader("Wechatpay-Signature");
        String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");
        String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");
        String wechatPayCertificateSerialNumber = httpServletRequest.getHeader("Wechatpay-Serial");
        String signType = httpServletRequest.getHeader("Wechatpay-Signature-Type");
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(wechatPayCertificateSerialNumber)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(signType)
                .body(getBodyString(httpServletRequest))
                .build();
        log.info("微信支付回调请求，微信支付通知请求参数：{}", requestParam);
        return requestParam;
    }

    /**
     * 微信jsapi支付通知验签
     *
     * @param httpServletRequest
     * @return
     */
    public static Transaction jsPayCheckSign(HttpServletRequest httpServletRequest) {
        // 验签并解密报文
        RequestParam requestParam = getRequestParam(httpServletRequest);
        try {
            NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
            Transaction decryptObject = parser.parse(requestParam, Transaction.class);
            log.info("jsapi支付回调报文信息：{} ", decryptObject);
            if (Transaction.TradeStateEnum.SUCCESS.name().equals(decryptObject.getTradeState().name())) {
                return decryptObject;
            }
            log.error("jsapi回调检查支付失败======");
            return null;
        }catch (Exception e){
            if (requestParam.getSignature().startsWith("WECHATPAY/SIGNTEST/")) {
                log.info("微信探测流量");
            }else {
                log.error("微信jsapi支付验签失败",e);
            }
            return null;
        }
    }

    /**
     * 微信App支付通知验签
     *
     * @param httpServletRequest
     * @return
     */
    public static com.wechat.pay.java.service.partnerpayments.app.model.Transaction appPayCheckSign(HttpServletRequest httpServletRequest) {
        // 验签并解密报文
        RequestParam requestParam = getRequestParam(httpServletRequest);
        try {
            NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
            com.wechat.pay.java.service.partnerpayments.app.model.Transaction decryptObject = parser.parse(requestParam, com.wechat.pay.java.service.partnerpayments.app.model.Transaction.class);
            log.info("app支付回调报文信息：{} ", decryptObject);
            if (Transaction.TradeStateEnum.SUCCESS.name().equals(decryptObject.getTradeState().name())) {
                return decryptObject;
            }
            log.error("app回调检查支付失败======");
            return null;
        }catch (Exception e){
            if (requestParam.getSignature().startsWith("WECHATPAY/SIGNTEST/")) {
                log.info("微信探测流量");
            }else {
                log.error("微信app支付验签失败",e);
            }
            return null;
        }
    }

    /**
     * 微信退款验签
     *
     * @param httpServletRequest
     * @return
     */
    public static RefundNotification wxRefundCheckSign(HttpServletRequest httpServletRequest) {
        // 验签并解密报文
        RequestParam requestParam = getRequestParam(httpServletRequest);
        try {
            NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
            RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class);
            log.info("微信支付退款通知回调报文信息：{} ", refundNotification);
            if (Status.SUCCESS.equals(refundNotification.getRefundStatus())) {
                return refundNotification;
            }
            log.error("微信支付退款通知回调报文信息检查支付失败======");
            return null;
        }catch (Exception e){
            if (requestParam.getSignature().startsWith("WECHATPAY/SIGNTEST/")) {
                log.info("微信探测流量");
            }else {
                log.error("微信退款验签失败",e);
            }
            return null;
        }
    }

    /**
     * 支付订单查询
     *
     * @param outTradeNo outTradeNo 商户订单号
     * @return com.wechat.pay.java.service.payments.model.Transaction
     */
    public static com.wechat.pay.java.service.payments.model.Transaction queryOrder(String outTradeNo) {
        JsapiService  jsapiService  = new JsapiService.Builder().config(rsaAutoCertificateConfig).build();
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setOutTradeNo(outTradeNo);
        request.setMchid(wxPayV3Config.getMchId());
        com.wechat.pay.java.service.payments.model.Transaction result = jsapiService.queryOrderByOutTradeNo(request);
        log.info("查询订单结果：{}", result);
        return result;
    }
}
